class List {
    constructor(arr = []) {
        this.array = arr;
        this.isLinked = false;
        this.isSnapList = true;

        this.car = null;
        this.cdr = null;
    }

    toLinked() {
        if (this.isLinked) return;

        this.isLinked = true;
        return this;
    }

    toArray() {
        if (!this.isLinked) return;

        this.array = this.asArray();
        this.isLinked = false;
        this.car = null;
        this.cdr = null;
        return this;
    }

    getArray() {
        if (this.isLinked) {
            var res = [];

            for (let v of this) {
                res.push(v);
            }

            return res;
        } else {
            return this.array;
        } 
    }

    asArray() {
        if (this.isLinked) {
            var res = [];

            for (let v of this) {
                res.push(v);
            }

            return new List(res);
        } else {
            return new List([...this.array]);
        }
    }

    // 1-indexed
    map(f) {
        if (this.isLinked) {
            // linked
            var cur = this;
            var i = 1;

            while (cur && cur.car !== null) {
                cur.car = f(cur.car, i, cur);
                i++;
                cur = cur.cdr;
            }

        } else {
            // array
            var res = [];

            for (let i = 0; i < this.array.length; i++) {
                res.push(f(this.array[i], i+1, this));
            }

            return new List(res);
        }
    }

    // 0-indexed
    _map(f) {
        if (this.isLinked) {
            // linked
            var res = new List().toLinked();
            var cur = this;
            var i = 0;

            while (cur && cur.car !== null) {
                res.car = f(cur.car, i++, cur);
                res = res.cdr = new List().toLinked();
                cur = cur.cdr;
            }

            return res;
        } else {
            return new List(this.array.map(f));
        }
    }

    filter(f) {
        if (this.isLinked) {
            // linked
            var cur = this;
            var res = new List();
            var i = 1;
            res.isLinked = true;

            while (cur && cur.car !== null) {
                if (f(cur.car, i, cur)) {
                    res.car = cur.car;
                    res = res.cdr = new List().toLinked();
                    cur = cur.cdr;
                }

                i++;
            }

        } else {
            // array
            var res = [];
            for (let i = 0; i < this.array.length; i++) {
                if (f(this.array[i], i+1, this)) {
                    res.push(this.array[i]);
                }
            }

            return new List(res);
        }
    }

    find(f) {
        if (this.isLinked) {
            // linked
            var cur = this;
            var i = 1;

            while (cur && cur.car !== null) {
                if (f(cur.car, i, cur)) {
                    return cur.car;
                }

                i++;
                cur = cur.cdr;
            }

            return "";
        } else {
            // array
            for (let i = 0; i < this.array.length; i++) {
                if (f(this.array[i], i+1, this)) {
                    return this.array[i];
                }
            }

            return "";
        }
    }

    reduce(f) {
        if (this.isLinked) {
            // linked
            var res = this.car;
            var cur = this.cdr;

            while (cur && cur.car !== null) {
                res = f(res, cur.car);
                cur = cur.cdr;
            }

            return res === null ? "" : res;
        } else {
            // array
            var res = this.array[0];

            for (let i = 1; i < this.array.length; i++) {
                res = f(res, this.array[i]);
            }

            return res;
        }
    }

    add(val) {
        this.toArray();
        this.array.push(val);
    }

    delete(i) {
        this.toArray();

        switch(i) {
            case "last":
                this.array.pop();
                break;
            case "all":
                this.array = [];
                break;
            default:
                this.array.splice(+i-1, 1);
                break;
        }
    }

    insert(i, val) {
        this.toArray();

        switch(i) {
            case "last":
                this.array.pop();
                break;
            case "random":
                this.array.splice(Math.floor(Math.random() * this.array.length), 0, val);
                break;
            default:
                this.array.splice(+i-1, 0, val);
        }
    }

    set(i, val) {
        this.toArray();

        switch(i) {
            case "last":
                this.array[this.array.length-1] = val;
                break;
            case "random":
                this.array[Math.floor(Math.random() * this.array.length)] = val;
                break;
            default:
                this.array[+i-1] = val;
        }
    }

    get(i) {
        if (+i === 1) {
            if (this.isLinked) return this.car;
            return this.array[0];
        }

        this.toArray();

        var res;

        switch(i) {
            case "last":
                res = this.array[this.array.length-1];
                break;
            case "random": case "any":
                res = this.array[Math.floor(Math.random() * this.array.length)];
                break;
            default:
                res = this.array[+i-1];
                break;
        }

        return res;
    }

    _get(i) {
        this.toArray();
        return this.array[i];
    }

    indexOf(val) {
        if (this.isLinked) {
            // linked
            var cur = this;
            var i = 1;

            while (cur && cur.car !== null) {
                if (cur.car === val) return i;
                i++;
                cur = cur.cdr;
            }
        } else {
            // array
            return this.array.indexOf(val) + 1;
        }
    }

    contains(val) {
        if (this.isLinked) {
            var cur = this;

            while (cur && cur.car !== null) {
                if (cur.car === val) return true;
            }

            return false;
        } else {
            return this.array.includes(val);
        }
    }

    isEmpty() {
        if (this.isLinked) {
            return this.car === null;
        } else {
            return this.array.length === 0;
        }
    }

    cons(car) {
        var res = new List().toLinked();
        res.car = car;
        res.cdr = this;
        return res;
    }

    getRest() {
        this.toLinked();

        return this.cdr === null ? new List() : this.cdr;
    }

    get length() {
        if (this.isLinked) {
            var cur = this;
            var len = 0;

            while (cur && cur.car !== null) {
                len++;
                cur = cur.cdr;
            }

            return len;
        } else {
            return this.array.length;
        }
    }

    *[Symbol.iterator]() {
        if (this.isLinked) {
            // linked
            var cur = this;

            while (cur && cur.car !== null) {
                yield cur.car;
                cur = cur.cdr;
            }
        } else {
            // array
            for (let v of this.array) {
                yield v;
            }
        }
    }
}

export { List };